package com.emeraldparser.symbol;

import com.emeraldparser.util.Automaton;
import com.emeraldparser.util.Automatons;
import com.google.common.base.Objects;

/**
 * A symbol class whose automaton contains at least two vertices:
 * 
 *  <ul>
 *  <li>the state "before" encountering a symbol of this symbol class</li>
 *  <li>the state "after" encountering a symbol of this symbol class</li>
 *  </ul>
 * 
 * @author Adam Paynter
 * 
 */
public abstract class TwoStateSymbolClass extends AbstractSymbolClass {

	private final SymbolClass.State beforeState = new BeforeState();
	private final SymbolClass.State afterState = new AfterState();

	protected SymbolClass.State getBeforeState() {
		return beforeState;
	}

	protected SymbolClass.State getAfterState() {
		return afterState;
	}

	/**
	 * Returns an automaton with exactly two states (the "before" and "after" states).
	 * The "before" state is the automaton's only start state. The "after" state is the
	 * automaton's only accept state. No edges are defined. Child classes are free to modify
	 * the automaton.
	 */
	@Override
	public Automaton<? extends State, ? extends SymbolClass> toAutomaton() {
		Automaton<State, SymbolClass> automaton = Automatons.create(super.toAutomaton());
		
		automaton.addVertex(beforeState);
		automaton.addStartVertex(beforeState);
		
		automaton.addVertex(afterState);
		automaton.addAcceptVertex(afterState);
		
		return automaton;
	}

	private class BeforeState implements SymbolClass.State {
		
		private final transient int hashCode;
		
		private BeforeState() {
			this.hashCode = Objects.hashCode(162499583, TwoStateSymbolClass.this);
		}

		@Override
		public String toString() {
			return "." + TwoStateSymbolClass.this;
		}
		
		@Override
		public int hashCode() {
			return hashCode;
		}

		@Override
		public boolean equals(Object obj) {
			if (this == obj) {
				return true;
			}
			
			if (obj == null) {
				return false;
			}
			
			if (getClass() != obj.getClass()) {
				return false;
			}
			
			BeforeState other = (BeforeState) obj;
			
			return Objects.equal(TwoStateSymbolClass.this, other.getSymbolClass());
		}

		private TwoStateSymbolClass getSymbolClass() {
			return TwoStateSymbolClass.this;
		}

	}

	private class AfterState implements SymbolClass.State {
		
		private final transient int hashCode;
		
		private AfterState() {
			this.hashCode = Objects.hashCode(49596437, TwoStateSymbolClass.this);
		}

		@Override
		public String toString() {
			return TwoStateSymbolClass.this + ".";
		}
		
		@Override
		public int hashCode() {
			return hashCode;
		}

		@Override
		public boolean equals(Object obj) {
			if (this == obj) {
				return true;
			}
			
			if (obj == null) {
				return false;
			}
			
			if (getClass() != obj.getClass()) {
				return false;
			}
			
			AfterState other = (AfterState) obj;
			
			return Objects.equal(TwoStateSymbolClass.this, other.getSymbolClass());
		}

		private TwoStateSymbolClass getSymbolClass() {
			return TwoStateSymbolClass.this;
		}

	}

}
